package org.coco.inject;

import org.coco.core.BeanContainer;
import org.coco.inject.annotation.Autowired;
import org.coco.mybatis.proxy.MapperProxy;
import org.coco.util.ClassUtil;
import org.coco.util.ValidationUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Set;

/**
 * 提供依赖注入的服务
 */
public class DependencyInjector {

    private static final Logger log = LoggerFactory.getLogger(DependencyInjector.class);

    /**
     * bean的容器
     */
    private BeanContainer beanContainer;

    public DependencyInjector() {
        beanContainer = BeanContainer.getInstance();
    }

    /**
     * 执行IOC
     */
    public void doIoc(){
        Set<Class<?>> classes = beanContainer.getClasses();
        if(ValidationUtil.isEmpty(classes)){
            log.warn("beanMap is null");
            return;
        }
        //1：遍历容器中所有的class对象
        for(Class<?> clazz : classes) {
            Object newInstance = beanContainer.getBean(clazz);
            //2：遍历class对象所有的成员变量
            Field[] declaredFields = clazz.getDeclaredFields();
            if(ValidationUtil.isEmpty(declaredFields)) {
                log.warn(clazz.getName() + " not have any field");
                continue;
            }
            for(Field field : declaredFields) {
                //3：找出被@Autowired标记的成员变量
                if (field.isAnnotationPresent(Autowired.class)) {
                    Autowired autowired = field.getAnnotation(Autowired.class);
                    String autowiredValue = autowired.value();
                    //4：获取这些成员变量的类型
                    Class<?> fieldClass = field.getType();
                    //5：获取这些成员变量的类型在容器中的实例
                    Object fieldValue = getFieldInstance(fieldClass,autowiredValue);
                    if (fieldValue == null) {
                        //无法从容器中获取该实例，那么就判断下是不是dao层的接口，因为这些接口是MyBatis调用sql的方法
                        Package aPackage = fieldClass.getPackage();
                        if("com.coco.dao".equals(aPackage.getName()) && fieldClass.isInterface()) {
                            //那么就要为该接口生成代理类,并注入到当前类中
                            Object proxy = MapperProxy.newMapperProxy(fieldClass);
                            ClassUtil.setField(field, newInstance, proxy, true);
                        }else {
                            throw new RuntimeException("Unable to inject relevant type ,target field class is:" + fieldClass.getName());
                        }
                    } else {
                        //6：通过反射将对应的成员变量实例注入到成员变量所在类的实例里
                        ClassUtil.setField(field, newInstance, fieldValue, true);
                    }
                }
            }
        }

    }

    /**
     * 根据class在beanContainer里获取其实例或者是实现类
     *
     * @param fieldClass
     * @return
     */
    private Object getFieldInstance(Class<?> fieldClass, String autowiredValue) {
        Object fieldValue = beanContainer.getBean(fieldClass);
        if(fieldValue != null) {
            //说明该成员变量的类型就是一个类
            return fieldValue;
        }else {
            //如果为空，有可能是一个接口，所以需要去获取对应的实现类
            Class<?> implementClass = getImplementClass(fieldClass,autowiredValue);
            if(implementClass != null) {
                return beanContainer.getBean(implementClass);
            }else {
                return null;
            }
        }
    }

    /**
     * 获取接口的实现类
     * @param fieldClass
     * @return
     */
    private Class<?> getImplementClass(Class<?> fieldClass, String autowiredValue) {
        Set<Class<?>> classesSet = beanContainer.getClassesBySuper(fieldClass);
        if(!ValidationUtil.isEmpty(classesSet)) {
           if(ValidationUtil.isEmpty(autowiredValue)) {
              if(classesSet.size() == 1) {
                 return classesSet.iterator().next();
              }else {
                  //如果多于二个实现类并且用户未指定其中的一个实现类，则抛出异常
                  throw new RuntimeException("该接口的实现类多于1个，不知道您需要那个？？请设置@Autowired的值");
              }
           }else {
                for(Class<?> clazz : classesSet) {
                    if(autowiredValue.equals(clazz.getSimpleName())) {
                        return clazz;
                    }
                }
           }
        }
        return null;
    }
}
